跳到主要内容

MySQL 学习(1)体系结构

结构一览

如何使用 MySQL 固然重要,但是就像学习 Java那样,不了解 JVM 很多东西都寸步难行,所以这里把 MySQL 的基本结构做个复盘

自上而下可以分为网络连接层,服务层(核心层),存储引擎层,物理文件层

Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。

而存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。

网络连接层

负责 MySQL 和其他应用程序的连接交互,包括连接管理、授权认证、安全等。由客户端程序连接和 MySQL服务器上的连接池组成。

每个客户端连接都对应着服务器上的一个线程,MySQL服务器上维护了一个线程池,避免为每个连接都创建销毁一个线程。当客户端连接到 MySQL服务器时,服务器对其进行认证。登录认证后,还要验证客户端是否有执行某个查询的操作权限。这一层并不是 MySQL所特有的技术。

使用连接池的必要性:

每个连接对应一个线程,实际业务中通常有许多个连接访问数据库服务器,如果每次连接都要创建一个新的线程,连接释放则销毁线程,对于系统损耗是非常大的。通过连接池维护和缓存一定的连接,由应用程序动态地对池中的连接进行申请、使用和释放,减少了内存损耗,提升了资源利用率。

服务层(核心层)

连接器

连接器主要和身份认证和权限相关的功能相关,就好比一个级别很高的门卫一样。

主要负责用户登录数据库,进行用户的身份认证,包括校验账户密码,权限等操作,如果用户账户密码已通过,连接器会到权限表中查询该用户的所有权限,之后在这个连接里的权限逻辑判断都是会依赖此时读取到的权限数据,也就是说,后续只要这个连接不断开,即时管理员修改了该用户的权限,该用户也是不受影响的。

查询缓存

查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。

查询缓存主要用来缓存我们所执行的 SELECT 语句以及该语句的结果集。

连接建立后,执行查询语句的时候,会先查询缓存,MySQL 会先校验这个 sql 是否执行过,以 Key-Value 的形式缓存在内存中,Key 是查询预计,Value 是结果集。如果缓存 key 被命中,就会直接返回给客户端,如果没有命中,就会执行后续的操作,完成后也会把结果缓存起来,方便下一次调用。当然在真正执行缓存查询的时候还是会校验用户的权限,是否有该表的查询条件。

MySQL 查询不建议使用缓存,因为查询缓存失效在实际业务场景中可能会非常频繁,假如你对一个表更新的话,这个表上的所有的查询缓存都会被清空。对于不经常更新的数据来说,使用缓存还是可以的。

提示

查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因此很可能你费劲地把结果存起来,还没使用呢,就被一个更新全清空了。对于更新压力大的数据库来说,查询缓存的命中率会非常低。除非你的业务就是有一张静态表,很长时间才会更新一次。比如,一个系统配置表,那这张表上的查询才适合使用查询缓存。(但是 MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说8.0开始彻底没有这个功能了)

所以,一般在大多数情况下我们都是不推荐去使用查询缓存的。MySQL 8.0 版本后删除了缓存的功能,官方也是认为该功能在实际的应用场景比较少,所以干脆直接删掉了。

分析器

MySQL 没有命中缓存,那么就会进入分析器,分析器主要是用来分析 SQL 语句是来干嘛的,分析器也会分为几步:

第一步,词法分析,一条 SQL 语句有多个字符串组成,首先要提取关键字,比如 select,提出查询的表,提出字段名,提出查询条件等等。做完这些操作后,就会进入第二步。

第二步,语法分析,主要就是判断你输入的 sql 是否正确,是否符合 MySQL 的语法。

备注

如果表 T 中没有字段 k,而执行了这个语句 select *from T where k=1, 那肯定是会报 “不存在这个列”的错误: Unknown column 'k' in 'where clause'。这个是在 MySQL 中的哪个阶段报错的?

在 MySQL 中,这个错误会在解析阶段报告。解析阶段是 SQL 查询处理的早期阶段,负责解析和验证查询语句的结构和语法正确性。当 MySQL 解析器在解析查询语句时遇到 "WHERE" 子句中引用了不存在的列 "k",它会生成该错误消息:"Unknown column 'k' in 'where clause'"。这意味着 MySQL 无法识别列 "k",并且在查询解析期间发现了语法错误。

完成这 2 步之后,MySQL 就准备开始执行了,但是如何执行,怎么执行是最好的结果呢?这个时候就需要优化器上场了。

优化器

经过了分析器,MySQL就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。

优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。比如你执行下面这样的语句,这个语句是执行两个表的 join:

-- 关键字 "using" 用于指定表连接中的列
-- 这里的 "using(ID)" 表示使用 "ID" 列作为连接条件来将表 "t1" 和 "t2" 连接起来。
-- 这意味着只有在两个表中的 "ID" 列具有相同的值时,相关的行才会被连接。
select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20;
  • 既可以先从表 t1 里面取出 c=10 的记录的 ID 值,再根据 ID 值关联到表 t2,再判断 t2 里面 d 的值是否等于 20。
  • 也可以先从表 t2 里面取出 d=20 的记录的 ID 值,再根据 ID 值关联到 t1,再判断 t1 里面 c 的值是否等于 10。

这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择哪种执行方法。可以说,经过了优化器之后可以说这个语句具体该如何执行就已经定下来。

执行器

当选择了执行方案后,MySQL 就准备开始执行了,首先执行前会校验该用户有没有权限,如果没有权限,就会返回错误信息,如果有权限,就就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。

select * from T where ID = 10

比如我们这个例子中的表 T 中,ID 字段没有索引,那么执行器的执行流程是这样的:

1、调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;

2、调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。

3、执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。

至此,这个语句就执行完成了。

对于有索引的表,执行的逻辑也差不多。第一次调用的是 “取满足条件的第一行” 这个接口,之后循环取 “满足条件的下一行” 这个接口,这些接口都是引擎中已经定义好的。

你会在数据库的慢查询日志中看到一个 rows_examined 的字段,表示这个语句执行过程中扫描了多少行。这个值就是在执行器每次调用引擎获取数据行的时候累加的。

在有些场景下,执行器调用一次,在引擎内部则扫描了多行,因此引擎扫描行数跟 rows_examined 并不是完全相同的。

一条查询语句是如何执行的?

说了以上这么多,那么究竟一条 sql 语句是如何执行的呢?其实我们的 sql 可以分为两种,一种是查询,一种是更新(增加,更新,删除)。我们先分析下查询语句,语句如下:

select * from tb_student  A where A.age='18' and A.name=' 张三';

分析下这个语句的执行流程:

1、先检查该语句是否有权限,如果没有权限,直接返回错误信息,如果有权限,在 MySQL8.0 版本以前,会先查询缓存,以这条 sql 语句为 key 在内存中查询是否有结果,如果有直接缓存,如果没有,执行下一步。

2、通过分析器进行词法分析,提取 sql 语句的关键元素,比如提取上面这个语句是查询 select,提取需要查询的表名为 tb_student,需要查询所有的列,查询条件是这个表的 id='1'。然后判断这个 sql 语句是否有语法错误,比如关键词是否正确等等,如果检查没问题就执行下一步。

3、接下来就是优化器进行确定执行方案,上面的 sql 语句,可以有两种执行方案:

A:先查询学生表中姓名为 “张三” 的学生,然后判断是否年龄是 18。

B:先找出学生中年龄 18 岁的学生,然后再查询姓名为 “张三” 的学生。

那么优化器根据自己的优化算法进行选择执行效率最好的一个方案(优化器认为,有时候不一定最好)。那么确认了执行计划后就准备开始执行了。

4、进行权限校验,如果没有权限就会返回错误信息,如果有权限就会调用数据库引擎接口,返回引擎的执行结果。

graph LR 权限校验 --如果命中缓存--> 查询缓存 权限校验 --> 分析器 查询缓存 --> 分析器 分析器 --> 优化器 优化器 --> 再一次权限校验 再一次权限校验 --> 执行器 执行器 --> 引擎

一条更新语句是如何执行的?

更新语句如何执行的呢?

sql 语句如下:

update tb_student A set A.age='19' where A.name='张三';

来给张三修改下年龄,其实条语句也基本上会沿着上一个查询的流程走,只不过执行更新的时候要记录日志,这就会引入日志模块了,MySQL 自带的日志模块式 binlog(归档日志) ,所有的存储引擎都可以使用,常用的 InnoDB 引擎还自带了一个日志模块 redolog(重做日志),就以 InnoDB 模式下来探讨这个语句的执行流程。

流程如下

1、先查询到张三这一条数据,如果有缓存,也是会用到缓存。

2、然后拿到查询的语句,把 age 改为 19,然后调用引擎 API 接口,写入这一行数据,InnoDB 引擎把数据保存在内存中,同时记录 redo log,此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交。

3、执行器收到通知后记录 binlog,然后调用引擎接口,提交 redo log 为提交状态。

4、更新完成。

重要的日志模块:redo log

不知道你还记不记得《孔乙己》这篇文章,酒店掌柜有一个粉板,专门用来记录客人的赊账记录。如果赊账的人不多,那么他可以把顾客名和账目写在板上。但如果赊账的人多了,粉板总会有记不下的时候,这个时候掌柜一定还有一个专门记录赊账的账本

如果有人要赊账或者还账的话,掌柜一般有两种做法:

  • 一种做法是直接把账本翻出来,把这次赊的账加上去或者扣除掉;
  • 另一种做法是先在粉板上记下这次的账,等打烊以后再把账本翻出来核算。

在生意红火柜台很忙时,掌柜一定会选择后者,因为前者操作实在是太麻烦了。首先,你得找到这个人的赊账总额那条记录。你想想,密密麻麻几十页,掌柜要找到那个名字,可能还得带上老花镜慢慢找,找到之后再拿出算盘计算,最后再将结果写回到账本上。

这整个过程想想都麻烦。相比之下,还是先在粉板上记一下方便。你想想,如果掌柜没有粉板的帮助,每次记账都得翻账本,效率是不是低得让人难以忍受?

同样,在 MySQL 里也有这个问题,如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程 IO 成本、查找成本都很高。为了解决这个问 题,MySQL 的设计者就用了类似酒店掌柜粉板的思路来提升更新效率。

而粉板和账本配合的整个过程,其实就是 MySQL 里经常说到的 WAL 技术,WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志,再写磁盘,也就是先写粉板,等不忙的时候再写账本。

具体来说,当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log(粉板)里面,并更新内存,这个时候更新就算完成了。同时,InnoDB引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做,这就像打烊以后掌柜做的事。

如果今天赊账的不多,掌柜可以等打烊后再整理。但如果某天赊账的特别多,粉板写满了,又怎么办呢?这个时候掌柜只好放下手中的活儿,把粉板中的一部分赊账记录更新到账本中,然后把这些记录从粉板上擦掉,为记新账腾出空间。

与此类似,InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“粉板”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示

write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。 checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文 件。

write pos 和 checkpoint 之间的是“粉板”上还空着的部分,可以用来记录新的操作。如果 write pos 追上 checkpoint,表示“粉板”满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推进一下。

有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe。要理解 crash-safe 这个概念,可以想想我们前面赊账记录的例子。只要赊账记录记在了粉板上或写在了账本上,之后即使掌柜忘记了,比如突然停业几天,恢复生意后依然可以通过账本和粉板上的数据明确赊账账目。

重要的日志模块:binlog

前面我们讲过,MySQL 整体来看,其实就有两块:一块是 Server 层,它主要做的是 MySQL 功能层面的事情;还有一块是引擎层,负责存储相关的具体事宜。上面我们聊到的粉板 redo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己的日志,称为 binlog(归档日志)。

为什么要用两个日志模块,用一个日志模块不行吗?

这是因为最开始 MySQL 并没有 InnoDB 引擎 ,MySQL 自带的引擎是 MyISAM,但是 MyISAM 没有 crash-safe 的能力,binlog 日志只能用于归档。而 InnoDB 是另一个公司以插件形式引入 MySQL 的,既然只依靠 binlog 是没有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系统,也就是 redo log 来实现 crash-safe 能力。

但是 redolog 是 InnoDB 引擎特有的,其他存储引擎都没有,这就导致会没有 crash-safe 的能力,binlog 日志只能用来归档。

两种日志的区别

这两种日志有以下三点不同。

  1. redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。

  2. redo log 是物理日志,记录的是 “在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如 “给 ID = 2 这一行的 c 字段加 1”。

  3. redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写” 是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

再看 update 语句时的内部流程

sql 语句如下:

update tb_student A set A.age='19' where A.name='张三';
  1. 执行器先找引擎取 A.name='张三' 这一行。name 是主键,引擎直接用树搜索找到这一行。如果 A.name='张三' 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。

  2. 执行器拿到引擎给的行数据,把这个值设置为 19,比如原来是 N,现在就是 19,得到新的一行数据,再调用引擎接口写入这行新数据。

  3. 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。

  4. 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。

  5. 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。

给出这个 update 语句的执行流程图,深色框表示是在执行器中执行的。

graph TD A[取 name='张三' 这一行] --> B{数据页在内存中 ?}; B -- 是 --> C[返回行数据]; C --> D[将这行设置为 19]; D --> E[写入新行]; E --> F[新行更新到内存]; F --> G[写入 redo log, 其处于 prepare 阶段]; G --> H[写入 binlog]; H --> I[提交事务]; I --> J[redo log 状态改为 commit 状态]; B -- 否则 --> K[从磁盘读入内存]; K --> L[返回给执行器]; L --> D; classDef green fill:#008000,stroke:#333,stroke-width:2px; class A,D,E,H green;

你可能注意到了,最后三步看上去有点“绕”,将 redo log 的写入拆成了两个步骤:prepare 和 commit,这就是"两阶段提交"。

两阶段提交是什么?

为什么必须有“两阶段提交”呢?这是为了让两份日志之间的逻辑一致。

因为如果先写 redo log 直接提交,然后写 binlog,假设写完 redo log 后,机器挂了,binlog 日志没有被写入,那么机器重启后,这台机器会通过 redo log 恢复数据,但是这个时候 bingog 并没有记录该数据,后续进行机器备份的时候,就会丢失这一条数据,同时主从同步也会丢失这一条数据。

如果先写 binlog,然后写 redo log,假设写完了 binlog,机器异常重启了,由于没有 redo log,本机是无法恢复这一条记录的,但是 binlog 又有记录,那么和上面同样的道理,就会产生数据不一致的情况。

而采用 redo log 两阶段提交的方式就不一样了,写完 binlog 后,然后再提交 redo log 就会防止出现上述的问题,从而保证了数据的一致性。那么问题来了,有没有一个极端的情况呢?假设 redo log 处于预提交状态,binlog 也已经写完了,这个时候发生了异常重启会怎么样呢? 这个就要依赖于 MySQL 的处理机制了,MySQL 的处理过程如下:

判断 redo log 是否完整,如果判断是完整的,就立即提交。 如果 redo log 只是预提交但不是 commit 状态,这个时候就会去判断 binlog 是否完整,如果完整就提交 redo log, 不完整就回滚事务。

这样就解决了数据一致性的问题。

存储引擎层

MySQL 数据库区别于其他数据库的最重要的一个特点就是其插件式的表存储引擎,从上图中也可以看到,MySql支持很多种存储引擎。需要特别注意的是,存储引擎是基于表的,而不是数据库。

所以每个表都可以设置为不同的引擎,以满足业务需求

插件式存储引擎的好处是:能够根据具体的应用的特点选择不同的存储引擎。下面是几种 MySQL 常用的存储引擎。

查看当支持的全部存储引擎

SHOW ENGINES

在 MySQL 中,不需要在整个服务器中使用同一种存储引擎,针对具体的要求,可以对每一个表使用不同的存储引擎。Support列的值表示某种引擎是否能使用:YES表示可以使用、NO表示不能使用、DEFAULT表示该引擎为当前默认的存储引擎。

如果要想查看数据库默认使用哪个引擎,可以通过使用命令:

-- 使用 SHOW VARIABLES 可以显示全部的变量
SHOW VARIABLES LIKE 'default_storage_engine';

InnoDB存储引擎

InnoDB是事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和外键,上图也看到了,InnoDB是默认的MySQL引擎。

InnoDB 主要特性

InnoDB 为 MySQL 提供了具有提交、回滚和崩溃恢复能力的 事务安全(ACID兼容)存储引擎。InnoDB 锁定在行级并且也在 SELECT 语句中提供一个类似 Oracle 的非锁定读。这些功能增加了多用户部署和性能。在 SQL 查询中,可以自由地将 InnoDB 类型的表和其他 MySQL 的表类型混合起来,甚至在同一个查询中也可以混合

InnoDB 存储引擎为在 主内存中缓存数据和索引而维持它自己的缓冲池。InnoDB 将它的表和索引在一个逻辑表空间中,表空间可以包含数个文件(或原始磁盘文件)。这与 MyISAM 表不同,比如在 MyISAM 表中每个表被存放在分离的文件中。InnoDB 表可以是任何尺寸,即使在文件尺寸被限制为2GB的操作系统上

InnoDB 支持 外键完整性约束,存储表中的数据时,每张表的存储都按主键顺序存放,如果没有显示在表定义时指定主键,InnoDB 会为每一行生成一个6字节的 ROWID(行 Id),并以此作为主键

使用 InnoDB 存储引擎 MySQL 将在数据目录下创建一个名为 ibdata1 的 10MB 大小的 自动扩展数据文件,以及两个名为 ib_logfile0 和 ib_logfile1 的 5MB 大小的日志文件

MyISAM存储引擎

MyISAM 基于 ISAM 存储引擎,并对其进行扩展。它是在 Web、数据仓储和其他应用环境下最常使用的存储引擎之一。MyISAM 拥有较高的插入、查询速度,但不支持事务

MyISAM 存储引擎不支持事务、表锁设计,支持全文索引,主要面向一些 OLAP 数据库应用。此外,MyISAM 存储引擎的另一个与众不同的地方是它的缓冲池 只缓存(cache)索引文件,而不缓冲数据文件,这点和大多数的数据库都非常不同。从MySQL 5.0版本开始,MyISAM 默认支持256TB 的单表数据,这足够满足一般应用需求。

使用 MyISAM 引擎创建数据库,将产生3个文件。文件的名字以表名字开始,扩展名之处文件类型:frm 文件存储表定义、数据文件的扩展名为 .MYD(MYData)、索引文件的扩展名时 .MYI(MYIndex)

MEMORY存储引擎

Memory存储引擎(之前称HEAP存储引擎)将表中的数据存放在内存中,如果数据库重启或发生崩溃,表中的数据都将消失。它非常适合用于存储临时数据的临时表,以及数据仓库中的纬度表。Memory存储引擎默认使用哈希索引,而不是我们熟悉的B+树索引。

虽然 Memory存储引擎速度非常快,但在使用上还是有一定的限制。比如,只支持表锁,并发性能较差,并且不支持 TEXT 和 BLOB列类型。最重要的是,存储变长字段(varchar)时是按照定常字段(char)的方式进行的,因此会浪费内存。

此外有一点容易被忽视,MySQL 数据库使用 Memory 存储引擎作为临时表来存放查询的中间结果集(intermediate result)。如果中间结果集大于 Memory 存储引擎表的容量设置,又或者中间结果含有 TEXT或 BLOB列类型字段,则 MySQL数据库会把其转换到 MyISAM存储引擎表而存放到磁盘中。之前提到 MyISAM不缓存数据文件,因此这时产生的临时表的性能对于查询会有损失。

存储引擎的选择

种存储引擎都有自己的优缺点,不能笼统地说谁比谁好。但通常建议选择使用 InnoDB

InnoDB: 支持事务处理,支持外键,支持崩溃修复能力和并发控制。如果需要对事务的完整性要求比较高(比如银行),要求实现并发控制(比如售票),那选择InnoDB有很大的优势。如果需要频繁的更新、删除操作的数据库,也可以选择 InnoDB,因为支持事务的提交(commit)和回滚(rollback)。

MyISAM: 插入数据快,空间和内存使用比较低。如果表主要是用于插入新记录和读出记录,那么选择 MyISAM 能实现处理高效率。如果应用的完整性、并发性要求比较低,也可以使用。

MEMORY: 所有的数据都在内存中,数据的处理速度快,但是安全性不高。如果需要很快的读写速度,对数据的安全性要求较低,可以选择 MEMORY。它对表的大小有要求,不能建立太大的表。所以,这类数据库只使用在相对较小的数据库表。

值得一提的是,MySQL 数据库使用 Memory 存储引擎作为临时表来存放查询的中间结果集(intermediate result)。 如果中间结果集大于 Memory 存储引擎表的容量设置,又或者中间结果含有 TEXT 或 BLOB 列类型字段,则 MySQL 数据库会把其转换到 MyISAM 存储引擎表而存放到磁盘。但是因为 MyISAM 不缓存数据文件,因此这时产生的临时表的性能对于查询会有损失。

物理文件层

参考资料 MySQL物理文件(数据目录)体系结构详解

在 MySQL 中,物理文件存放在数据目录中。数据目录与安装目录不同,安装目录用来存储控制服务器和客户端程序的命令,数据目录用来存储 MySQL 服务器在运行过程中产生的数据。本节主要介绍 MySQL 数据目录的物理结构和作用。

该层主要将数据库的数据(表,索引等)存储在文件系统上,并完成与存储引擎的交互。存储数据包括日志文件,数据文件,配置文件等。

注意:MySQL 中任何一项逻辑性或者物理性文件都具有可配置性,另外每个版本都有一些改进,所以在学习本节内容时要灵活掌握,不能生搬硬套。

可以通过下面这个命令查看 mysql 的文件路径

SHOW VARIABLES LIKE 'datadir';

数据目录

Data 目录用来存放数据库相关的数据信息,包括数据库信息,表信息等。 my.ini 文件是 MySQL 服务端和客户端主要的配置文件,包括编码集、默认引擎、最大连接数等设置。MySQL 服务器启动时会默认加载此文件。

Data目录

由图中可以看出,系统数据库和用户自定义数据库的存放路径相同。数据库目录中主要存放相应的数据库对象

对 Data 目录中的文件说明如下:

mysql、performance_schema、sys 和 world 是系统数据库,information_schema 数据库比较特殊,这里没有相应的数据库目录。

auto.cnf:MySQL 服务器的选项文件,用于存储 server-uuid 的值。server-uuid 与 server-id 一样,用于标识 MySQL 实例在集群中的唯一性。

ib_logfile0、ib_logfile1 是支持事务性引擎的 redo 日志文件

ibdata1 为共享表空间(系统表空间)。如果采用 InnoDB 引擎,默认大小为 10M

数据目录里可能还有:

MySQL 服务器的进程 ID(PID)文件。 MySQL 服务器所生成的状态和日志文件。 DES 密钥文件或服务器的 SSL 证书。

数据库目录

数据库实际是一个目录,每个目录都保存着相应数据库中的表以及表数据。下面以 test 数据库(自定义的一个数据库)为例讲解目录中存放的文件。

test 数据库中有如下几张数据表:

+-------------------+
| Tables_in_test |
+-------------------+
| tb_student |
| tb_student_course |
| tb_students_info |
| tb_usertest |
+-------------------+

对 test 数据库目录中的文件说明如下:

1、db.opt:用来保存数据库的配置信息,比如该库的默认字符集编码和字符集排序规则。如果你创建数据库时指定了字符集和排序规则,后续创建的表没有指定字符集和排序规则,那么该表将采用 db.opt 文件中指定的属性。

对于 InnoDB 表,如果是独立的表空间,数据库中的表结构以及数据都存储在数据库的路径下(而不是在共享表空间 ibdata1 文件中)。但是数据中的其他对象,包括数据被修改之后,事务提交之间的版本信息,仍然存储在共享表空间的 ibdata1 文件中。

2、.frm:在 MySQL 中建立任何一张数据表,其对应的数据库目录下都会有该表的 .frm 文件。.frm 文件用来保存每个数据表的元数据(meta)和表结构等信息。数据库崩溃时,可以用 .frm 文件恢复表结构。

.frm 文件跟存储引擎无关,任何存储引擎的数据表都有 .frm 文件,命名方式为 表名.frm,如 users.frm

MySQL 8.0 版本开始,frm 文件被取消,MySQL 把文件中的数据都写到了系统表空间。通过利用 InnoDB 存储引擎来实现表 DDL 语句操作的原子性(在之前版本中是无法实现表 DDL 语句操作的原子性的,如 TRUNCATE 无法回滚)。

3、.MYD.MYI 文件

  • .MYD 理解为 My Data,用于存放 MyISAM 表的数据。
  • .MYI 理解为 My Index,主要存放 MyISAM 表的索引及相关信息。

5、.ibd.ibdata 文件

对于 InnoDB 存储引擎的数据表,一个表对应两个文件,一个是 *.frm,存储表结构信息;一个是 *.ibd,存储表中数据。.ibd.ibdata 都是专属于 InnoDB 存储引擎的数据库文件。当采用共享表空间时,所有 InnoDB 表的数据均存放在 .ibdata 中。所以当表越来越多时,这个文件会变得很大。相对应的 .ibd 就是采用独享表空间时 InnoDB 表的数据文件。

当然,就算开启了独享表空间,ibdata 文件也会越来越大,因为这个文件里还存储了:

  • 变更缓冲区
  • 双写缓冲区
  • 撤销日志

References